/*
 * Copyright 2008 Jens Meiss
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.hmvc4java.component;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.googlecode.hmvc4java.ComponentStatus;
import com.googlecode.hmvc4java.controller.IController;
import com.googlecode.hmvc4java.controller.IControllerModel;
import com.googlecode.hmvc4java.controller.IControllerModelView;
import com.googlecode.hmvc4java.controller.IControllerView;
import com.googlecode.hmvc4java.event.IEvent;
import com.googlecode.hmvc4java.event.TerminateEvent;
import com.googlecode.hmvc4java.model.IModel;
import com.googlecode.hmvc4java.view.IView;

/**
 * 
 * 
 * @author jens.meiss
 */
public abstract class AComponent implements IComponent {

	private static final byte mapChildComponentInitialCapacity = 5;

	private IComponent componentParent;

	private IController controller;

	private List<Class<? extends IEvent>> listListeningEvents = new LinkedList<Class<? extends IEvent>>();

	private List<IComponent> listTerminatedChildComponent;

	private Map<Class<? extends IController>, List<IComponent>> mapChildComponents;

	private ComponentStatus status = ComponentStatus.NEW;

	public AComponent(IController controller) {
		setController(controller);
	}

	public <M extends IModel> AComponent(M model, IControllerModel<M> controller) {
		controller.setModel(model);
		setController(controller);
	}

	public <M extends IModel, V extends IView> AComponent(M model, V view,
			IControllerModelView<M, V> controller) {
		controller.setModel(model);
		controller.setView(view);
		setController(controller);
	}

	public <V extends IView> AComponent(V view, IControllerView<V> controller) {
		controller.setView(view);
		setController(controller);
	}

	public final void addChildComponent(IComponent component) {

		if ((getStatus() == ComponentStatus.NEW)
				|| (getStatus() == ComponentStatus.TERMINATING)
				|| (getStatus() == ComponentStatus.TERMINATED)) {
			return;
		}

		/*
		 * set parentComponent
		 */
		component.setParentComponent(this);

		/*
		 * add childComponent to the mapChildComponents
		 */
		if (getMapChildComponents() == null) {
			/*
			 * creating the mapChildComponents
			 */
			setMapChildComponents(new HashMap<Class<? extends IController>, List<IComponent>>(
					mapChildComponentInitialCapacity));
		}

		List<IComponent> listChildComponents = null;
		if (getMapChildComponents().containsKey(
				component.getController().getClass())) {
			/*
			 * retrieve the list from the mapChildComponents
			 */
			listChildComponents = getMapChildComponents().get(
					component.getController().getClass());
		} else {
			/*
			 * create a new list for controller of the controller-class
			 */
			listChildComponents = new LinkedList<IComponent>();
			getMapChildComponents().put(component.getController().getClass(),
					listChildComponents);
		}

		listChildComponents.add(component);

		/*
		 * initialize the childComponent
		 */
		component.initComponent();

	}

	public void addTerminadtedChild(IComponent childComponent) {

		if ((getStatus() == ComponentStatus.NEW)
				|| (getStatus() == ComponentStatus.TERMINATED)) {
			return;
		}

		if (getListTerminatedChildComponent() == null) {
			setTerminatedListChildComponent(new LinkedList<IComponent>());
		}

		getListTerminatedChildComponent().add(childComponent);
	}

	public final boolean containsChildComponent(
			Class<? extends IController> classOfController) {

		/*
		 * remove terminated child first
		 */
		removeTerminatedChildComponents();

		if ((getStatus() == ComponentStatus.NEW)
				|| (getStatus() == ComponentStatus.TERMINATED)
				|| (getMapChildComponents() == null)
				|| (getMapChildComponents().size() == 0)) {

			return false;
		}

		if (getMapChildComponents().containsKey(classOfController)) {

			if (getMapChildComponents().get(classOfController).size() > 0) {
				return true;
			} else {
				return false;
			}

		} else {
			return false;
		}

	}

	public final boolean containsChildComponent(IController controller) {

		/*
		 * remove terminated child first
		 */
		removeTerminatedChildComponents();

		if ((getStatus() == ComponentStatus.NEW)
				|| (getStatus() == ComponentStatus.TERMINATED)
				|| (getMapChildComponents() == null)) {
			return false;
		}

		/*
		 * contains the map a list for controllers of the controller-class
		 */
		if (getMapChildComponents().containsKey(controller.getClass())) {

			/*
			 * contains the list the searched controller?
			 */
			if (((List<IComponent>) getMapChildComponents().get(
					controller.getClass())).contains(controller.getComponent())) {
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}

	}

	public final void fireEventDown(IEvent event) {

		if ((getStatus() == ComponentStatus.NEW)
				|| (getStatus() == ComponentStatus.TERMINATED)) {
			return;
		}

		if (getMapChildComponents() == null) {
			return;
		}

		Iterator<List<IComponent>> iteratorListChildComponents = getMapChildComponents()
				.values().iterator();
		while (iteratorListChildComponents.hasNext()) {

			Iterator<IComponent> iteratorChildComponents = ((List<IComponent>) iteratorListChildComponents
					.next()).iterator();

			while (iteratorChildComponents.hasNext()) {
				IComponent component = (IComponent) iteratorChildComponents
						.next();
				/*
				 * was the event handled by the childComponent?
				 */
				if (component.receiveEvent(event)) {
					/*
					 * stop the handling?
					 */
					if (event.stopHandling()) {
						return;
					}
				} else {
					// TODO try "breadth first search" instead of
					// "depth-first search"
					component.fireEventDown(event);
				}

			}

		}
	}

	public final void fireEventUp(IEvent event) {

		if ((getStatus() == ComponentStatus.NEW)
				|| (getStatus() == ComponentStatus.TERMINATED)) {
			return;
		}

		/*
		 * exist a parenteComponent
		 */
		if (getParentComponent() != null) {
			/*
			 * was the event handled?
			 */
			if (getParentComponent().receiveEvent(event)) {
				/*
				 * should the event handling stop after the handling?
				 */
				if (event.stopHandling()) {
					/*
					 * yes
					 */
					return;
				} else {
					/*
					 * no, fire the event up to all other listeners
					 */
					getParentComponent().fireEventUp(event);
				}
			} else {
				/*
				 * the event wasn't handled by the parentComponent, fire it up
				 * to the parentComponent of the parentComponent
				 */
				getParentComponent().fireEventUp(event);
			}
		}

	}

	/**
	 * @return the controller
	 */
	public final IController getController() {
		return controller;
	}

	/**
	 * @return the listListeningEvents
	 */
	public List<Class<? extends IEvent>> getListListeningEvents() {
		return listListeningEvents;
	}

	public List<IComponent> getListTerminatedChildComponent() {
		return listTerminatedChildComponent;
	}

	/**
	 * @return the mapChildComponents
	 */
	private Map<Class<? extends IController>, List<IComponent>> getMapChildComponents() {
		return mapChildComponents;
	}

	/**
	 * @return the componentParent
	 */
	public final IComponent getParentComponent() {
		return componentParent;
	}

	public ComponentStatus getStatus() {
		return status;
	}

	@SuppressWarnings("unchecked")
	public void initComponent() {

		if (getStatus() != ComponentStatus.NEW) {
			return;
		}

		setStatus(ComponentStatus.INITIALIZING);

		/*
		 * connect controller and component
		 */
		getController().setComponent(this);

		/*
		 * call the init method of the component and the controller
		 */
		init();

		/*
		 * call initModel if the Controller is a instanceof IControllerModel
		 */
		if (getController() instanceof IControllerModel) {
			((IControllerModel) getController()).initModel();
		}

		/*
		 * call initView if the Controller is a instanceof IControllerView
		 */
		if (getController() instanceof IControllerView) {
			((IControllerView) getController()).initView();
		}

		getController().init();

		setStatus(ComponentStatus.RUNNING);

	}

	public boolean receiveEvent(IEvent event) {

		if ((getStatus() == ComponentStatus.NEW)
				|| (getStatus() == ComponentStatus.TERMINATED)) {
			return false;
		}

		if (getListListeningEvents().contains(event.getClass())) {

			/*
			 * first remove all terminated childComponents
			 */
			removeTerminatedChildComponents();

			/*
			 * then handle the event :)
			 */
			handleEvent(event);
			getController().handleEvent(event);

			return true;
		}

		/*
		 * is it an TerminateEvent
		 */
		if (event.getClass() == TerminateEvent.class) {
			/*
			 * Terminate the Component
			 */
			terminateComponent();
		}

		return false;
	}

	public final void register(Class<? extends IEvent> classOfEvent) {

		if ((getStatus() == ComponentStatus.NEW)
				|| (getStatus() == ComponentStatus.TERMINATED)) {
			return;
		}

		if (!getListListeningEvents().contains(classOfEvent)) {
			getListListeningEvents().add(classOfEvent);
		}
	}

	private void removeTerminatedChildComponents() {

		if (getListTerminatedChildComponent() == null) {
			return;
		}

		if (getListTerminatedChildComponent().size() > 0) {

			/*
			 * iterating over all terminatedChildComponents to remove them from
			 * the mapChildComponent
			 */
			Iterator<IComponent> iteratorTerminatedChildComponents = getListTerminatedChildComponent()
					.iterator();
			while (iteratorTerminatedChildComponents.hasNext()) {

				IComponent terminatedChildComponent = (IComponent) iteratorTerminatedChildComponents
						.next();

				/*
				 * remove the terminated ChildComponent
				 */
				List<IComponent> listChildComponents = getMapChildComponents()
						.get(
								terminatedChildComponent.getController()
										.getClass());
				listChildComponents.remove(terminatedChildComponent);

			}
		}

		getListTerminatedChildComponent().clear();

	}

	/**
	 * @param controller
	 *            the controller to set
	 */
	public final void setController(IController controller) {
		if (getController() == null) {
			this.controller = controller;
		}
	}

	/**
	 * @param mapChildComponents
	 *            the mapChildComponents to set
	 */
	private void setMapChildComponents(
			Map<Class<? extends IController>, List<IComponent>> mapChildComponents) {
		this.mapChildComponents = mapChildComponents;
	}

	/**
	 * @param componentParent
	 *            the componentParent to set
	 */
	public final void setParentComponent(IComponent component) {
		if (getParentComponent() == null) {
			this.componentParent = component;
		}
	}

	private void setStatus(ComponentStatus status) {
		this.status = status;
	}

	private void setTerminatedListChildComponent(
			List<IComponent> listChildComponent) {
		this.listTerminatedChildComponent = listChildComponent;
	}

	public void terminateComponent() {

		if ((getStatus() == ComponentStatus.TERMINATING)
				|| (getStatus() == ComponentStatus.TERMINATED)) {
			return;
		}

		setStatus(ComponentStatus.TERMINATING);

		/*
		 * terminate the controller
		 */
		getController().terminate();

		/*
		 * terminate the component
		 */
		terminate();

		setStatus(ComponentStatus.TERMINATED);

		/*
		 * add the component to the list of terminated childComponents of the
		 * parentComponent
		 */
		if (getParentComponent() != null) {
			getParentComponent().addTerminadtedChild(this);
		}

	}
}
